﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Web;
using up6.db.biz;
using up6.db.model;
using up6.db.utils;
using up6.utils;

namespace api.up6
{
    public partial class f_post : WebBase
    {
        bool safe_check(params string[] ps)
        {
            foreach (var v in ps)
            {
                System.Diagnostics.Debug.Write("参数值：");
                System.Diagnostics.Debug.WriteLine(v);
                if (string.IsNullOrEmpty(v)) return false;
            }
            foreach (string key in Request.Headers.Keys)
            {
                var vs = Request.Headers.GetValues(key);
                //XDebug.Output(key + " "+String.Join(",", vs));
            }
            return true;
        }

        void send_error(string errCode,JObject par=null) {
            this.toContentJson(
                new JObject {
                    { "msg",errCode},
                    { "code",errCode},
                    { "md5","" },//文件块MD5
                    { "offset", -1 },//偏移
                    { "param", par},//参数信息
                });
        }

        /// <summary>
        /// 验证块大小，并解密块数据
        /// </summary>
        /// <param name="blockEncrypt"></param>
        /// <param name="blockSizeCry"></param>
        /// <param name="stm"></param>
        /// <param name="fileSvr"></param>
        /// <returns></returns>
        bool checkBlockSize(bool blockEncrypt,int blockSizeCry,ref Stream stm,ref FileInf fileSvr) {
            //块加密
            if (blockEncrypt)
            {
                //加密块大小不相同
                if (stm.Length != blockSizeCry)
                    this.send_error(UploadErrorCode.blockSizeCryDifferent,
                        new JObject {
                                {"blockSizeCryRecv",stm.Length },
                                {"blockSizeCrySend",blockSizeCry},
                        });

                CryptoTool ct = new CryptoTool();
                fileSvr.pathSvr = ct.decode(fileSvr.pathSvr);
                fileSvr.pathSvr = fileSvr.pathSvr.Replace("\0", "");
                //不加密=>还原数据
                if (!fileSvr.encrypt) stm = ct.decode(stm, fileSvr.blockSize);
            }//文件块大小不相同
            else if (stm.Length != fileSvr.blockSize)
            {
                this.send_error(UploadErrorCode.blockSizeDifferent,
                            new JObject {
                                    { "sizeRecv",stm.Length},
                                    { "sizeSend",fileSvr.blockSize},
                            });
                return false;
            }
            return true;
        }

        /// <summary>
        /// 验证块MD5
        /// </summary>
        /// <param name="blockMd5"></param>
        /// <param name="stm"></param>
        /// <returns></returns>
        bool checkBlockMd5(string blockMd5,ref string md5Svr,Stream stm) {
            if (!string.IsNullOrEmpty(blockMd5))
            {
                md5Svr = Md5Tool.calc(stm);
                if (md5Svr != blockMd5)
                    this.send_error(UploadErrorCode.blockMd5Different,
                        new JObject {
                                "md5Svr",md5Svr,
                                "md5Loc",blockMd5
                        });
                return false;
            }
            return true;
        }

        bool checkToken(string token,FileInf fileSvr) {
            WebSafe ws = new WebSafe();
            if (!ws.validToken(token, fileSvr, "block"))
            {
                this.send_error(UploadErrorCode.blockMd5Different,
                    new JObject {
                        "action","block"
                    });
                return false;
            }
            return true;
        }

        /// <summary>
        /// 只负责拼接文件块。将接收的文件块数据写入到文件中。
        /// 更新记录：
        ///     2022-11-13 优化参数
        ///     2017-07-11 优化参数检查逻辑
        ///     2016-03-31 增加文件夹信息字段
        ///		2015-03-19 文件路径由客户端提供，此页面不再查询文件在服务端的路径。减少一次数据库访问操作。
        ///		2012-04-12 更新文件大小变量类型，增加对2G以上文件的支持。
        ///		2012-10-30 增加更新文件进度功能。
        ///		2012-04-18 取消更新文件上传进度信息逻辑。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            string uid          = this.headString("uid");
            string f_id         = this.headString("id");
            string pid          = this.headString("pid");
            string pidRoot      = this.headString("pidRoot");
            long lenSvr       = this.headLong("lenSvr");//已传大小
            long lenLoc       = this.headLong("lenLoc");//本地文件大小
            long blockOffset  = this.headLong("blockOffset");
            int blockSize    = this.headInt("blockSize");//当前块大小
            int blockSizeCry = this.headInt("blockSizeCry");//加密块大小
            bool blockEncrypt = this.headBool("blockEncrypt");//是否加密
            int blockIndex   = this.headInt("blockIndex");//当前块索引，基于1
            int blockCount   = this.headInt("blockCount");//块总数
            string blockMd5     = this.headString("blockMd5");//块MD5
            var complete        = this.headBool("complete");//true/false
            string object_id     = this.headString("object_id");//
            string pathSvr      = Request.Form["pathSvr"];//
            string pathRel      = Request.Form["pathRel"];//parentDir/filename.ext
            string pathLoc      = Request.Form["pathLoc"];
            string token      = this.headString("token");//
            pathSvr = Server.UrlDecode(pathSvr);
            pathRel = Server.UrlDecode(pathRel);
            pathLoc = Server.UrlDecode(pathLoc);
            bool isLastBlock = complete;

            if ( !this.safe_check(uid,f_id,pathSvr)) return;

            //有文件块数据
            if (Request.Files.Count > 0)
            {
                string md5Svr = string.Empty;
                HttpPostedFile file = Request.Files.Get(0);//文件块
                var stm = file.InputStream;
                if(string.IsNullOrEmpty(pathLoc)) pathLoc = file.FileName;

                FileInf fileSvr = new FileInf();
                fileSvr.id = f_id;
                fileSvr.pid = pid;
                fileSvr.pidRoot = pidRoot;
                fileSvr.object_id = object_id;
                fileSvr.lenSvr = lenSvr;
                fileSvr.lenLoc = lenLoc;
                fileSvr.pathLoc = pathLoc;
                fileSvr.pathSvr = pathSvr;
                fileSvr.pathRel = pathRel;
                fileSvr.blockIndex = blockIndex;
                fileSvr.blockOffset = blockOffset;
                fileSvr.blockSize=blockSize;//原始块大小
                fileSvr.blockCount = blockCount;
                fileSvr.nameLoc = PathTool.getName(pathLoc);
                fileSvr.nameSvr = fileSvr.nameLoc;
                fileSvr.encrypt = ConfigReader.storageEncrypt();//加密存储

                //验证块并解密块
                this.checkBlockSize(blockEncrypt, blockSizeCry, ref stm, ref fileSvr);

                //token验证
                this.checkToken(token, fileSvr);

                //计算文件块MD5
                this.checkBlockMd5(blockMd5,ref md5Svr, stm);

                PathBuilder pb = new PathBuilder();
                fileSvr.pathSvr = pb.relToAbs(fileSvr.pathSvr);

                var fw = ConfigReader.blockWriter();
                bool needGenId = !string.IsNullOrEmpty(pid);
                if (needGenId) needGenId = 1 == fileSvr.blockIndex;
                if (needGenId && 
                    (fw.storage == StorageType.FastDFS||
                    fw.storage == StorageType.Minio||
                    fw.storage == StorageType.OSS ||
                    fw.storage == StorageType.OBS)) 
                    needGenId = string.IsNullOrEmpty(object_id);
                    
                try
                {
                    //第一块->创建或生成对象ID
                    if (needGenId)
                    {
                        fileSvr.object_id = fw.make(fileSvr);
                        fileSvr.object_key = fileSvr.getObjectKey();
                    }

                    //子文件=>保存到层级信息文件
                    if (!string.IsNullOrEmpty(fileSvr.pidRoot)) fileSvr.saveScheme();

                    //写入块数据
                    fw.write(fileSvr, stm);

                    //合并文件
                    if (isLastBlock) fw.writeLastPart(fileSvr);

                }
                catch (IOException ie) {
                    this.send_error(UploadErrorCode.writeBlockDataFail,
                        new JObject { { "exception", ie.Message } });
                }

                //触发事件
                up6_biz_event.file_post_block(f_id,Convert.ToInt32(blockIndex));

                //生成信息
                JObject o = new JObject();
                o["msg"] = "ok";
                o["md5"] = md5Svr;//文件块MD5
                o["offset"] = blockOffset;//偏移
                //回传给控件
                if(blockIndex==1) o["fields"]= new JObject {
                    { "object_id", fileSvr.object_id}
                };
                var msg = JsonConvert.SerializeObject(o);
                this.toContentJson(msg);
            }
        }
    }
}